package wortubung.util;

import static wortubung.util.CommonUtils.*;
import static java.lang.Math.abs;

import java.util.List;

public class MatrixPrinter {

	public static class Matrix {
		public String[] titles;
		public Object[][] results;

		public Matrix(String[] titles, Object[][] results) {
			this.titles = titles;
			this.results = results;
		}
	}

	public static final String[] MATRIX_DIVIDER = new String[0];

	public enum STYLE { Clear, SQL };

    public static void printMatrix(Object[][] results) {
        printMatrix(null, STYLE.Clear, false, new Matrix(null, results), true, false);
    }

    public static void printMatrix(Printable printable, boolean hardDivider, boolean envelop, Matrix[] matrixSets, boolean firstColumnRight, boolean otherColumnsRight) {
    	printMatrix(printable, STYLE.Clear, hardDivider, envelop, matrixSets, getMatrixWidths(matrixSets, firstColumnRight, otherColumnsRight));
    }

    public static void printMatrix(Printable printable, boolean envelop, ImmutableStringList titles, Object[][] results) {
        printMatrix(printable, envelop, titles, results, false, false);
    }
    
    public static void printMatrix(Printable printable, boolean envelop, ImmutableStringList titles, Object[][] results, boolean firstColumnRight, boolean otherColumnsRight) {
        printMatrix(printable, STYLE.Clear, envelop, titles, results, firstColumnRight, otherColumnsRight);
    }

    public static void printMatrix(Printable printable, boolean envelop, String[] titles, Object[][] results, boolean firstColumnRight, boolean otherColumnsRight) {
        printMatrix(printable, envelop, new Matrix(titles, results), firstColumnRight, otherColumnsRight);
    }
    
    public static void printMatrix(Printable printable, boolean envelop, Matrix matrix, boolean firstColumnRight, boolean otherColumnsRight) {
        printMatrix(printable, STYLE.Clear, envelop, matrix, firstColumnRight, otherColumnsRight);
    }
    
    public static void printMatrix(Printable printable, STYLE style, boolean envelop, ImmutableStringList titles, Object[][] results) {
        printMatrix(printable, style, envelop, titles, results, false, false);
    }
    
    public static void printMatrix(Printable printable, STYLE style, boolean envelop, ImmutableStringList titles, Object[][] results, boolean firstColumnRight, boolean otherColumnsRight) {
    	printMatrix(printable, style, envelop, new Matrix(titles == null ? null : titles.toArray(), results), firstColumnRight, otherColumnsRight);
    }

    public static void printMatrix(Printable printable, STYLE style, boolean envelop, ImmutableStringList titles, Object[][] results, int[] widths) {
    	printMatrix(printable, style, false, envelop, new Matrix[]{ new Matrix(titles == null ? null : titles.toArray(), results) }, widths);
    }

    public static int[] getMatrixWidths(Matrix[] matrixSets, boolean firstColumnRight, boolean otherColumnsRight) {
    	int[] widths = null;
    	for (Matrix mtx : matrixSets) {
    		widths = getMaxLengths(widths, mtx.titles);
	    	if (length(mtx.results) > 0)
	    		for (Object[] sa : mtx.results)
	    			widths = getMaxLengths(widths, sa);
    	}

    	if (isNotEmpty(widths)) {
    		if (firstColumnRight)
    			widths[0] = -widths[0];
    		if (otherColumnsRight) {
    			for (int i=widths.length-1; i>=1; --i)
    				widths[i] = -widths[i];
    		}
    	}
    	return widths;
    }
    
    private static int[] getMaxLengths(int[] widths, Object[] oa) {
    	if (length(oa) == 0)
    		return widths;

    	int i, w;
    	if (widths == null) {
    		widths = new int[oa.length];
    		for (i=0; i<widths.length; ++i)
    			widths[i] = getWidth(oa[i]);
    		return widths;
    	}
    	
    	if (widths.length < oa.length) {
    		int[] tmp = new int[oa.length];
    		for (i=0; i<tmp.length; ++i)
    			tmp[i] = (i<widths.length) ? widths[i] : 0;
    		widths = tmp;
    	}
		for (i=0; i<widths.length; ++i)
			if ((w = getWidth(at(oa, i))) > widths[i])
				widths[i] = w;
		return widths;
    }

    public static void printMatrix(Printable printable, STYLE style, boolean envelop, Matrix matrix, boolean firstColumnRight, boolean otherColumnsRight) {
    	int[] widths = getMatrixWidths(new Matrix[]{ matrix }, firstColumnRight, otherColumnsRight);
    	if (widths != null)
    		printMatrix(printable, style, false, envelop, new Matrix[]{ matrix }, widths);
    }

    public static void printMatrix(Printable printable, STYLE style, boolean hardDivider, boolean envelop, List<Matrix> matrixSets, int[] widths) {
        printMatrix(printable, style, hardDivider, envelop, matrixSets.toArray(new Matrix[matrixSets.size()]), widths);
    }

    public static void printMatrix(Printable printable, STYLE style, boolean hardDivider, boolean envelop, Matrix[] matrixSets, int[] widths) {
    	if (isEmpty(matrixSets))
    		return;
    	if (printable == null)
    		printable = ToolBase.getSingleton();

    	// Get format and divider
    	StringBuilder fmtsb = new StringBuilder();
    	StringBuilder divsb = new StringBuilder();
    	StringBuilder envsb = new StringBuilder();
    	StringBuilder sepsb = new StringBuilder();
    	if (style == STYLE.Clear) {
    		fmtsb.append(' ');
    		divsb.append(' ');
    		if (!hardDivider)
    			sepsb.append("--");
    		if (envelop)
    			envsb.append("==");
    	}
    	int i, j;
    	for (i=0; i<widths.length; ++i) {
    		if (i>0) {
    			if (style == STYLE.SQL) {
    				fmtsb.append(" | ");
    				divsb.append("-+-");
    			} else {
    				fmtsb.append("  ");
    				divsb.append("  ");
    	    		if (!hardDivider)
    	    			sepsb.append("--");
    	    		if (envelop)
    	    			envsb.append("==");
    			}
    		}
    		fmtsb.append("%").append(-widths[i]).append('s');
    		for (j=abs(widths[i]); j>0; --j) {
    			divsb.append('-');
    			if (style == STYLE.Clear) {
    	    		if (!hardDivider)
    	    			sepsb.append('-');
    	    		if (envelop)
    	    			envsb.append('=');
    			}
    		}
    	}

    	String fmt = fmtsb.toString();
    	String div = divsb.toString();
    	String sep = sepsb.toString();
    	String env = style == STYLE.SQL ? div : envsb.toString();

    	if (envelop)
    		printable.println(env);
    	Matrix last = matrixSets[matrixSets.length-1];
    	for (Matrix mtx : matrixSets) {
	    	if (mtx.titles != null) {
	    		String[][] saa = toMultiLine(mtx.titles, true);
	    		for (int k=0; k<saa.length; ++k)
	    			printable.printfln(fmt, (Object[])saa[k]);
	    		if (isEmpty(at(mtx.titles, 0)))
    				printable.println(removeFirstDiv(div));
	    		else
	    			printable.println(div);
	    	}

	    	String[] buf = new String[widths.length];
	    	for (Object[] vals : mtx.results) {
	    		if (vals == null)
	    			continue;
	    		if (vals == MATRIX_DIVIDER) {
	    			printable.println(div);
	    			continue;
	    		}

	    		// if any value is longer than the width, they are turned into multiple lines
	    		int lineCnt = 1;
	    		int idx;
	    		String[][] saa = new String[vals.length][];
	    		for (j=0; j<vals.length; ++j) {
	    			Object val = vals[j];
	    			int valLen = length(val);
	    			int w = abs(at(widths, j, 0));
	    			if (w == 0)
	    				continue;
	    			int myLineCnt = valLen / w;
	    			if (myLineCnt * w < valLen)
	    				++myLineCnt;
	    			if (myLineCnt > lineCnt)
	    				lineCnt = myLineCnt;
	    			String[] lines = new String[myLineCnt <= 0 ? 1 : myLineCnt];
	    			for (idx=0; idx<myLineCnt; ++idx) {
	    				int start = idx * w;
	    				int end = start + w;
	    				if (end > length(val))
	    					lines[idx] = val.toString().substring(start);
	    				else
	    					lines[idx] = val.toString().substring(start, end);
	    			}
	    			saa[j] = lines;
	    		}

	    		for (int k=0; k<lineCnt; ++k) {
	    			for (idx=0; idx<widths.length; ++idx)
	   					buf[idx] = nn(saa, idx, k);
   				    for (String[] subRow : toMultiLine(buf, false))
	   					printable.printfln(fmt, (Object[])subRow);
	    		}
	    	}
	    	if (envelop) {
	    		if (hardDivider || mtx == last)
		    		printable.println(env);
		    	else
		    		printable.println(sep);
	    	}
    	}

   		printable.println("");
    }

    private static String removeFirstDiv(String div) {
    	int idx1 = div.indexOf('-');
    	int idx2 = div.indexOf(' ', idx1);
    	if (idx1 < 0 || idx2 <= idx1)
    		return div;
    	return div.substring(0, idx1) + makeStringLen("", 0, idx2-idx1, ' ') + div.substring(idx2);
    }

    private static String[][] toMultiLine(Object[] row, /*int[] widthds,*/ boolean valignBottom) {
    	String[][] saa = new String[row.length][];
    	int cols = row.length;
    	int i, j, layers = 1;
    	for (i=cols-1; i>=0; --i) {
    		if (row[i] == null)
    			continue;
    		saa[i] = row[i].toString().split("\n");
    		if (saa[i].length > layers)
    			layers = saa[i].length;
    	}
    	
    	String[][] ret = new String[layers][];
    	for (i=0; i<layers; ++i)
    		ret[i] = new String[row.length];
    	for (i=0; i<cols; ++i) {
    		int colLayers = length(saa[i]);
    		if (valignBottom) {
    			int delta = layers - colLayers;
    			for (j=0; j<delta; ++j)
    				ret[j][i] = "";
	    		for (j=0; j<colLayers; ++j)
	    			ret[j + delta][i] = nn(saa, i, j);
    		} else {
	    		for (j=0; j<colLayers; ++j)
	    			ret[j][i] = nn(saa, i, j);
	    		while (j < layers)
	    			ret[j++][i] = "";
    		}
    	}
    	return ret;
    }

//    public static void main(String[] args) {
//    	int[] widths = { 10, 10, 10};
//    	Object[] results = { "A", "0123456789\n56789", 249 };
//        printMatrix(null, STYLE.SQL, null, toMultiLine(results, true));
//    }
}
